package uk.co.cdal.cobweb.frontend.GUI;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;

import javax.swing.JPanel;
import javax.swing.event.MouseInputListener;

import uk.co.cdal.cobweb.backend.BackendImpl;
import uk.co.cdal.cobweb.frontend.Frontend;
import uk.co.cdal.cobweb.shared.Idea;

public class DrawingPanel extends JPanel {
	
	private BackendImpl backend;
	
	private HashMap<String, SpiderNode> spiderNodes;
	
	private SpiderLayout layout;
	
	private Rectangle outliner;
	private Image outlinerImage;
	
	private boolean inDrag;
	private boolean inLink;
	private SpiderNode nodeInDrag;
	private SpiderNode nodeInLink;
	
	private SpiderNode curSelected;
	
	private Color backgroundColor;
	private Color foregroundColor;
	private Color mouseOverColor;
	private Color borderColor;
	private Color edgeColor;
	private Color outlinerFill;
	private Color outlinerBorderColor;
	
	private int currMouseX;
	private int currMouseY;
	


	public DrawingPanel(BackendImpl backend) {
		//Set layout of super
		super(new SpiderLayout());
		
		//Set backend
		this.backend = backend;
		
		// initialise data structure
		spiderNodes = new HashMap<String, SpiderNode>();
		
		// Should return no ideas at this point but maybe will if opening a file.
		getIdeas();		
		
		// Set default colours
		backgroundColor = new Color(255,255,255);
		foregroundColor = new Color(0,0,0);
		mouseOverColor = new Color(179,190,255);
		borderColor = new Color(0,0,0);
		edgeColor = new Color(0,0,0);
		outlinerFill = new Color(179,190,255);
		outlinerBorderColor = Color.BLUE;
		
		// curSelected - null
		curSelected = null;
		
		// Set background
		setBackground(backgroundColor);
		
		// Set the drawing pnael visible
		setVisible(true);
		
		//set the auto scrolls
		setAutoscrolls(true);
		 MouseMotionListener doScrollRectToVisible = new MouseMotionAdapter() {
			 public void mouseDragged(MouseEvent e) {
		        Rectangle r = new Rectangle(e.getX(), e.getY(), 1, 1);
		        ((JPanel)e.getSource()).scrollRectToVisible(r);
		    }
		 };
		// Add the mouse listener
		addMouseListener(new DrawingPanelMouseListener());
		addMouseMotionListener(new DrawingPanelMouseListener());
		
		// Initially we are not dragging or linking so set the rel. vars.
		inDrag = false;
		inLink = false;
		
	}
	
	/**
	 * This method receives update calls from the backend when
	 * a new node is added - either locally or remotely
	 * 
	 * If the node is added locally the visual representation 
	 * will already exist.
	 * 
	 * If added remotely It will not therefore initilises it.
	 *
	 */
	public void getIdeas() {
		// Get ideas possibly contaning nulls
		ArrayList<Idea> ideas = backend.getIdeas();
		
		// Create SpiderNodes 
		for(int i = 0; i < ideas.size(); i++) {
			Idea n = ideas.get(i);
			SpiderNode sn;
			if(n != null) {
				if(!spiderNodes.containsKey(n.getId())) {
					// Make a new spider node - Remote ideas
					// Place new nodes in the middle of the map
					sn = new SpiderNode(getWidth() / 2, getHeight() /2, n);
					sn.setTitle(n.getTitle());
					sn.setText(n.getFurtherText());
					sn.setUrl(n.getUrls());
					sn.setImages(n.getImages());
					add(sn);
					spiderNodes.put(n.getId(), sn);
				}
//				else if(n.isChanged()) {
//					remove(spiderNodes.get(n.getId()));
//					sn = new SpiderNode(getWidth() / 2, getHeight() /2, n);
//					sn.setTitle(n.getTitle());
//					sn.setText(n.getFurtherText());
//					sn.setUrl(n.getUrls());
//					sn.setImages(n.getImages());
//					add(sn);
//					spiderNodes.put(n.getId(), sn);
//					n.setChanged(false);
//					
//					// request redraw of edge from parent
//					if(n.getParent() != null) {
//						spiderNodes.get(n.getParent().getId()).updateEdge(n.getId(), getGraphics());
//					}
//					
//					// Check if children have changed if so update edges
//					if(n.hasChildren()) {
//						ArrayList<Idea> children = n.getChildren();
//						for(int j = 0; j < children.size(); j++) {
//							if(children.get(j).isChanged()) {
//								spiderNodes.get(n.getId()).updateEdge(children.get(j).getId(), getGraphics());
//							}
//						}
//					}
//				}
//				
//				// Draw edges - Done from parent to child (simplicity)
//				if(n.hasChildren()) {
//					ArrayList<Idea> children = n.getChildren();
//					for(int j = 0; j < children.size(); j++) {
//						spiderNodes.get(n.getId()).addEdge(spiderNodes.get(children.get(j).getId()));
//					}
//				}
				
				// set the bounds of the SpiderNode
				Insets insets = getInsets();
				sn = spiderNodes.get(n.getId());
//				sn.setBounds(insets.left + sn.getX(), insets.top + sn.getY(), sn.getWidth(), sn.getHeight());
				repaint();
				invalidate();
			}
		}
	}
	
	/**
	 * Removes the specified ideas visualisation from the map.
	 * @param i the idea to be removed
	 */
	public void removeIdea(Idea i) {
		Component c = spiderNodes.get(i.getId());
		if(i.getParent() != null) {
			spiderNodes.get(i.getParent().getId()).removeEdge((SpiderNode)c);
		}
		spiderNodes.remove(i.getId());
		remove(c);
		validate();
		try {
			backend.removeIdea(i, backend.getAddress());
		}
		catch(RemoteException e) {
			backend.log.severe("Cannot resolve local host");
		}
	}
		
	public void addIdea(int x, int y) {
		Idea i = new Idea();
		common(x, y, i);
	}
	
	public void addIdea(int x, int y, Idea parent) {
		Idea i = new Idea(parent);
		parent.addChild(i);
		common(x, y, i);
	}
	
	private void common(int x, int y, Idea i) {
		try {
			backend.addIdea(i, backend.getAddress());
		}
		catch (RemoteException e) {
			BackendImpl.log("Cannot resolve local host");
		}
		SpiderNode sn = new SpiderNode(x,y,i);
		spiderNodes.put(i.getId(), sn);
		sn.setSize(sn.getPreferredSize());
		add(sn);
		this.getParent().validate();
		if(i.getParent() != null) {
			spiderNodes.get(i.getParent().getId()).addEdge(sn);
		}
	}
	
//	public void setOutliner(Rectangle outliner)
//	{
//		this.outliner = outliner;
//	}
	
	public void setOutliner(Rectangle outliner, Image outlinerImage) {
		this.outliner = outliner;
		this.outlinerImage = outlinerImage;
	}
	
	protected void paintComponent(Graphics g)
	{
		Graphics2D g2d = (Graphics2D) g.create();
		
		// Set antialiasing on
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
				RenderingHints.VALUE_ANTIALIAS_ON);		
		
		// Fill background
		g2d.setColor(getBackground());
		g2d.fillRect(0, 0, getWidth(), getHeight());
		
		// Draw edges
		g2d.setColor(borderColor);
		g2d.setStroke(new BasicStroke(3));
		//g2d.drawLine(100, 100, 400, 100);
		ArrayList<SpiderNode> sns = new ArrayList<SpiderNode>(spiderNodes.values());
		for(int i = 0; i < sns.size(); i++) {
			if(sns.get(i) != null) {
				ArrayList<Edge> edges = new ArrayList<Edge>(sns.get(i).getEdges().values()); 
				for(int j = 0; j < edges.size(); j++) {
					Edge e = edges.get(j);
					if(e.getParent() == nodeInDrag) {
						g2d.drawLine(outliner.x + (e.getParent().getWidth() / 2), outliner.y + (e.getParent().getHeight() / 2), e.getChild().getCenterX(), e.getChild().getCenterY());
					}
					else if(e.getChild() == nodeInDrag) {
						g2d.drawLine(e.getParent().getCenterX(), e.getParent().getCenterY(), outliner.x + (e.getChild().getWidth() / 2), outliner.y + (e.getChild().getHeight() / 2));
					}
					else if(spiderNodes.get(e.getParent().getId()) == null || spiderNodes.get(e.getChild().getId()) == null) {
						
					}
					else{
						g2d.drawLine(e.getParent().getCenterX(), e.getParent().getCenterY(), e.getChild().getCenterX(), e.getChild().getCenterY());
					}
				}
			}
		}
		
		// Draw outliner
		if (outliner != null) {
			if (outlinerImage != null) {
				g2d.drawImage(outlinerImage, outliner.x, outliner.y,
						outlinerFill, null);
			}
			else {
				g2d.setColor(outlinerBorderColor);
				g2d.fillRect(outliner.x, outliner.y, outliner.width-1, outliner.height-1);			
	
				g2d.setColor(outlinerFill);
				g2d.fillRect(outliner.x + 2, outliner.y + 2, outliner.width-1-4, outliner.height-1-4);			
			}
		}
		
		// Draw linking line (if any)
		if(inLink) {
			g2d.drawLine(nodeInLink.getCenterX(), nodeInLink.getCenterY(), currMouseX, currMouseY);
		}
		
		g2d.dispose();
	}
	
	/**
	 * The state of a node has changed and the change needs to be reflected to the
	 * Screen
	 *
	 */
	public void update() {
		getIdeas();
	}
	
	public void addTestIdeas() {
		addIdea(100, 100);
		
		addIdea(200,300);
	}
	
	/**
	 * @return the inDrag
	 */
	public boolean isInDrag() {
		return inDrag;
	}

	/**
	 * @param inDrag the inDrag to set
	 */
	public void setInDrag(boolean inDrag) {
		this.inDrag = inDrag;
	}
	

	/**
	 * @return the backgroundColor
	 */
	public Color getBackgroundColor() {
		return backgroundColor;
	}

	/**
	 * @param backgroundColor the backgroundColor to set
	 */
	public void setBackgroundColor(Color backgroundColor) {
		this.backgroundColor = backgroundColor;
	}

	/**
	 * @return the mouseOverColor
	 */
	public Color getMouseOverColor() {
		return mouseOverColor;
	}

	/**
	 * @param mouseOverColor the mouseOverColor to set
	 */
	public void setMouseOverColor(Color mouseOverColor) {
		this.mouseOverColor = mouseOverColor;
	}

	/**
	 * @return the borderColor
	 */
	public Color getBorderColor() {
		return borderColor;
	}

	/**
	 * @param borderColor the borderColor to set
	 */
	public void setBorderColor(Color borderColor) {
		this.borderColor = borderColor;
	}

	/**
	 * @return the edgeColor
	 */
	public Color getEdgeColor() {
		return edgeColor;
	}

	/**
	 * @param edgeColor the edgeColor to set
	 */
	public void setEdgeColor(Color edgeColor) {
		this.edgeColor = edgeColor;
	}

	/**
	 * @return the foregroundColor
	 */
	public Color getForegroundColor() {
		return foregroundColor;
	}

	/**
	 * @param foregroundColor the foregroundColor to set
	 */
	public void setForegroundColor(Color foregroundColor) {
		this.foregroundColor = foregroundColor;
	}

	/**
	 * @return the curSelected
	 */
	public SpiderNode getCurSelected() { 
		return curSelected;
	}

	/**
	 * @param curSelected the curSelected to set
	 */
	public void setCurSelected(SpiderNode curSelected) {
		Frontend.getFrontend().getMb().updateIdea(curSelected);
		this.curSelected = curSelected;
	}

	/**
	 * @return the inLink
	 */
	public boolean isInLink() {
		return inLink;
	}

	/**
	 * @param inLink the inLink to set
	 */
	public void setInLink(boolean inLink) {
		this.inLink = inLink;
	}

	/**
	 * @return the outlinerBorderColor
	 */
	public Color getOutlinerBorderColor() {
		return outlinerBorderColor;
	}

	/**
	 * @param outlinerBorderColor the outlinerBorderColor to set
	 */
	public void setOutlinerBorderColor(Color outlinerBorderColor) {
		this.outlinerBorderColor = outlinerBorderColor;
	}
	
	public class DrawingPanelMouseListener extends MouseAdapter implements MouseInputListener {

		/* (non-Javadoc)
		 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
		 */
		public void mouseClicked(MouseEvent e) {
			// If button is right button then produce 
			//TODO: Produce this code.
			
			//TODO:If button = mouse1 then draw translucent rectangle for selecting purposes.

		}
		
		public void mousePressed(MouseEvent e) {

		}

		/* (non-Javadoc)
		 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
		 */
		public void mouseReleased(MouseEvent e) {
			// TODO Auto-generated method stub
			if(e.getButton() == 1) {
				if(!inLink){
					Insets insets = getInsets();
					addIdea(e.getX()+insets.left, e.getY()+insets.top);
				}
				else if(inLink) {
					//TODO: Ask bruce about testing wether point exists
					addIdea(e.getX(), e.getY(), nodeInLink.getIdea());
					inLink = false;
					nodeInLink = null;
				}
			}
			setCurSelected(null);
		}

		public void mouseDragged(MouseEvent e) {
			// TODO Auto-generated method stub
			
		}

		public void mouseMoved(MouseEvent e) {
			currMouseX = e.getX();
			currMouseY = e.getY();
			repaint();
		}
	}

	/**
	 * @return the nodeInDrag
	 */
	public SpiderNode getNodeInDrag() {
		return nodeInDrag;
	}

	/**
	 * @param nodeInDrag the nodeInDrag to set
	 */
	public void setNodeInDrag(SpiderNode nodeInDrag) {
		this.nodeInDrag = nodeInDrag;
	}

	/**
	 * @return the nodeInLink
	 */
	public SpiderNode getNodeInLink() {
		return nodeInLink;
	}

	/**
	 * @param nodeInLink the nodeInLink to set
	 */
	public void setNodeInLink(SpiderNode nodeInLink) {
		this.nodeInLink = nodeInLink;
	}
}
